﻿using System;
using System.Text;
using System.Text.RegularExpressions;
using HtmlSanitizer;
using Microsoft.Security.Application;
using TidyNet;
using System.IO;

namespace HtmlSanitizer
{
    public class HtmlSanitizer
    {
        #region Private fields
        private static readonly Regex HtmlTagExpression = new Regex(@"
            (?'tag_start'</?)
            (?'tag'\w+)((\s+
            (?'attribute'(\w+)(\s*=\s*(?:"".*?""|'.*?'|[^'"">\s]+)))?)+\s*|\s*)
            (?'tag_end'/?>)", 
            RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace | RegexOptions.IgnoreCase | RegexOptions.Compiled);

        #endregion

        #region Public Members
        /// <summary>
        /// Removes the invalid HTML tags according the strategy selected for each tag.
        /// </summary>
        /// <param name="input">The text.</param>
        /// <returns></returns>
        public static string Process(string input)
        {
            Tag tag;
            var html = TidyHtml(input);

            if (string.IsNullOrEmpty(html))
                return AntiXss.HtmlEncode(input);

            return HtmlTagExpression.Replace(html, new MatchEvaluator(match =>
            {              
                string text = string.Empty;

                var tagStart = match.Groups["tag_start"];
                var tagEnd = match.Groups["tag_end"];
                var tagName = match.Groups["tag"].Value;
                var attributes = match.Groups["attribute"];

                if (TagsConfiguration.Instance.Tags.TryGetValue(tagName, out tag))
                {
                    text = tag.SanitizeStrategy.Execute(tagStart, tagEnd, tagName, attributes);  
                }
                else
                {
                    text = (new RemoveTagStrategy()).Execute(tagStart, tagEnd, tagName, attributes); 
                }

                return text;
            }));
        }
        #endregion

        #region Private Members
        private static string TidyHtml(string text)
        {
            var doc = new Tidy();
            var messages = new TidyMessageCollection();
            var input = new MemoryStream();
            var output = new MemoryStream();

            var array = Encoding.UTF8.GetBytes(text);
            input.Write(array, 0, array.Length);
            input.Position = 0;

            doc.Options.DocType = DocType.Omit;
            doc.Options.Xhtml = true;
            doc.Options.LogicalEmphasis = true;
            doc.Options.DropFontTags = true;
            doc.Options.DropEmptyParas = true;
            doc.Options.QuoteAmpersand = true;
            doc.Options.TidyMark = false;
            doc.Options.MakeClean = true;
            doc.Options.IndentContent = true;
            doc.Options.SmartIndent = true;
//            doc.Options.RawOut = true;
//            doc.Options.CharEncoding = CharEncoding.Raw;
            
            doc.Parse(input, output, messages);

            string tidyHtml = Encoding.UTF8.GetString(output.ToArray());

            if (string.IsNullOrEmpty(tidyHtml))
                throw new InvalidHtmlException(messages);

            return RemoveTidyAdditions(tidyHtml);
        }

        private static string RemoveTidyAdditions(string text)
        {

            Regex bodyExpression = new Regex(@"<body[^>]+>");
            if (bodyExpression.IsMatch(text))
            {
                Match match = bodyExpression.Match(text);
                var start = match.Index;
                var end = text.IndexOf("</body>");

                if (start != -1 && end > start && end < text.Length)
                    text = text.Substring(start + match.Length, end - (start + match.Length));
                else
                    return string.Empty;
            }

            return text;
        }
        #endregion
    }
}
